Basic4GL, Copyright (C) 2005 Tom Mulgrew
Network engine guide
2-Apr-2005
Tom Mulgrew
The Basic4GL network engine is designed primarily for writing
games.
It allows you to establish a network connection between two
running Basic4GL programs and send blocks of data (which we call
"messages") back and forth.
Basic4GL will attempt to send these messages as quickly as
possible (with some extra logic for reliability and/or ordering
if requested).
Actually that's all it really does.
Which leaves it up to you to decide what sort of data to send,
what it means, and how often you send it. :)
However with a bit of planning it is possible to write some
responsive multiplayer lan or internet games without too much
fuss.
The network engine uses UDP/IP packets for communication.
Any network that can do TCP/IP can do UDP/IP (as TCP is built on
top of UDP), so your programs can run over the Internet and
TCP/IP local networks.
Basic4GL uses its own protocol for handling connection
lifetime and reliable packet delivery which is optimised towards
writing responsive networked games. It lets you choose which
messages must get through and which ones don't matter if
they get lost on the way. You can also choose which messages must
arrive in the same order they were sent and which ones don't
matter so much.
This upshot of this is that a carefully designed application will
have the best chance to be able to continue smoothly if a data
packet is lost in transmission, which is important for realtime
games. (Unlike TCP/IP which has to stop for a few seconds if it
hits an error).
The downside is because the Basic4GL network engine uses its own
protocol, it can only talk to other applications using the same
protocol. In other words, other Basic4GL programs, so you cannot
use Basic4GL to write an FTP client, web browser, etc. (Standard
TCP/IP support will likely be added to a later version.)
The current Basic4GL network engine currently supports:
Note: The network engine (at time of writing) is still young and lacks some features found in more mature networking engines likeautomatic bandwidth throttling.
The bulk of a program's network code usually involves:
A network message is a block of data, similar to a small disk
file. Infact Basic4GL uses the file I/O functions to read and
write the contents of network messages.
Instead of using OpenFileWrite and OpenFileRead, you use
SendMessage and ReceiveMessage, but otherwise it's just like
accessing a disk file.
Compare this program to write a simple text file:
dim file file = OpenFileWrite ("files\test.txt") ' Open a file for output WriteString (file, "Some text") ' Write some text CloseFile (file) ' Close the file
With this program to send a message over a network connection:
dim msg ... msg = SendMessage (connection) ' Create a message to send down connection WriteString (msg, "Some text") ' Write some text CloseFile (msg) ' Send the message
(Note: The above program is incomplete...)
All of Basic4GL's file I/O functions except
"OpenFileRead" and "OpenFileWrite" can be
used with Basic4GL network messages.
These functions are described in the File I/O section of the
Basic4GL Programmer's Guide.
Two connect two computer over a network, you must do the following:
At this point both the client and the server have a
"connection" with which they can send and receive data.
Data sent down the server's connection will be received by the
client's connection and vice versa.
(Note: This can be extended to connect multiple computers together, by having one as the server and having the rest of them as clients that connect to the server. In this case the server will have multiple "connection"s, one for each client.)
Format:
NewServer (port)
Where port is the port number on which the server
"listen"s for connection requests.
NewServer() creates a server and returns a handle to identify the
server to other functions (such as AcceptConnection()).
Example:
dim server server = NewServer (8000) ' Create a new server on port 8000 ' ... Run the program DeleteServer (server) ' Close and delete the server
Format:
DeleteServer (server)
Where server is a server handle returned from
NewServer().
Shuts down and deletes the server. Any connections accepted by
the server will automatically be disconnected and deleted.
It is good practice to close server objects (and connections)
when finished with them.
If not closed explicitly, Basic4GL will automatically close them
when the program ends.
Format:
ConnectionPending (server)
Where server is a server handle returned from NewServer().
ConnectionPending() returns true if a client has asked for a
connection to the server and is waiting for the server to accept
or reject it.
The connection can now be accepted with AcceptConnection() or
rejected with RejectConnection().
Format:
AcceptConnection (server)
Where server is a server handle returned from NewServer().
AcceptConnection() accepts a pending connection request,
creates a corresponding connection object and returns a handle
for it.
If no connection is pending, AcceptConnection() does nothing and
returns 0.
Example:
const port = 8000 dim server, connection ' Create server server = NewServer (port) printr "Server created. Waiting for connections" ' Wait for incoming connections while true if ConnectionPending (server) then printr "Connection accepted" ' Accept connection connection = AcceptConnection (server) ' ... Do something here Sleep (1000) ' Close connection now that we're finished DeleteConnection (connection) endif wend
Format:
RejectConnection (server)
Where server is a server handle returned from NewServer().
Rejects an incoming connection request.
This returns a notification to the connecting client that the
connection has not been accepted, and discards
the request.
Format:
NewConnection (address, port)
Creates a new connection and attempts to connect to a server
at the specified address and port.
address is a text string specifying the network name to
connect to. It can either be a DNS address (e.g.
"someserver.com"), a numeric IP address (e.g.
"192.168.0.1") or "localhost" (meaning
connect to the same computer).
port is the port number. It must be the same one as the
server is listening on, otherwise it wont find the server.
NewConnection() returns a handle identifying the connection that can be passed to other functions (such as SendMessage()).
Format:
DeleteConnection (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
Deletes a network connection.
If the connection is active, it will be closed, and a
notification sent to the corresponding connection at the other
end to inform it of the close.
Basic4GL also automatically closes and deletes any outstanding network connections when the program finishes.
Format:
ConnectionConnected (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
ConnectionConnected() returns true if the connection is still
connected, or false if the connection has been disconnected.
Connections are considered "connected" when they are
created, and remain that way until either:
Format:
ConnectionHandshaking(connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
Returns true if the connection is in the hand-shaking state.
Connections created by NewConnection() are considered to be
"hand-shaking" until the server accepts the connection
(and the confirmation notification is received).
Once the connection is established, it leaves the hand-shaking
state (ConnectionHandshaking() will then return false), and the
connection is ready to send and receive messages.
Note: Server connections created with AcceptConnection() do not have a hand-shaking phase. For these ConnectionHandshaking() will always return false. The connection is fully established as soon as it has been accepted.
Data is passed through connections as "messages", variable length blocks of data which are transmitted and received as a single item.
Format:
SendMessage (connection, channel, reliable, smoothed)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
SendMessage() creates a message ready to be sent down connection,
and returns a handle representing the message.
You can then pass this handle to the Write...() file I/O
functions (WriteByte(), WriteString(), etc) to write data to the
message, just as you would write data to a file. Refer to
the file I/O functions in the Basic4GL Programmer's Guide
for more information.
Once the message is ready, call CloseFile() to close the message and send it.
SendMessage() has 3 options which affect message delivery:
1. Channel
Channel is a "channel number" and affects the order
in which messages are received.
Depending on network conditions messages can arrive at the
receiving end in a different order than which they were sent.
Also, in some cases a message (or part of a message) may be lost
in transmission and have to be resent, which delays the message
long enough for other messages to get in infront of it.
The Basic4GL network engine supports ordering of messages through
"channels". Every connection has 32 channels (numbered
0 through 31 inclusive). Messages sent within a single channel
are guaranteed to be received in the same order as
they were sent, with the exception of channel # 0 which is the
unordered channel.
Two messages sent down different channels are not guaranteed
to be received in the same order.
There are multiple channels to allow you to specify on which messages the ordering is important. A good choice of channels can affect network performance, especially over unreliable networks (such as an internet connection). If an ordered message is delayed, the whole channel will stall until the message is received and slotted into its correct order. However other channels will still keep receive messages. So if a game was using on ordered channel for chat messages, and a different channel for position updates, the engine can keep receiving position updates even if a chat message is lost and must be re-transmitted.
2. Reliable
Reliable is true if the message must be delivered.
Depending on network conditions, some messages may be lost in
transmission. The reliable flag specifies whether this is
acceptable for this message (reliable = false) or whether the
message must get through (in which case the
network engine will keep resending the packet until delivery is
confirmed).
3. Smoothed
The amount of time a message takes to reach its destination is
called the network latency (or "lag"). This can vary
greatly depending on the network connection. Over a local network
the latency can be just a few hundredths of a second. Over a
dial-up internet connection to the otherside of the world it can
be as much as a second.
Depending on network conditions the latency can actually from
message to message, meaning that messages (like position updates)
that are sent out at nice regular spaced out intervals may arrive
in at the other end in irregular clumps.
The Basic4GL network engine has a "smoothing"
algorithm which compensates this by measuring the time it takes
to deliver packets, and delaying early packets until they are
considered "due".
So if un"smoothed" messages have a network latency of
100-200ms, applying smoothing might cause the majority of
messages to have a latency of 180ms (with a few taking
180-200ms).
Be aware that this algorithm is effectively adding "lag" to faster messages, and should therefore be used with caution.
[Example here]
Format:
MessagePending (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessagePending() returns true if a message has been received and can be fetched with ReceiveMessage().
Format:
MessageChannel (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessageChannel() returns the channel number of the pending message. (See SendMessage() for more information).
Format:
MessageReliable (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessageReliable() returns whether the pending message was sent as a reliable message (MessageReliable() = true) or as an unreliable message. (See SendMessage() for more information).
Format:
MessageSmoothed (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessageSmoothed() returns whether the pending message was sent as a smoothed message (MessageSmoothed() = true) or not. (See SendMessage() for more information).
Format:
ReceiveMessage (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
ReceivedMessage() fetches the current pending message from the
connection and returns a handle representing the message.
You can then pass this handle to the Read...() file I/O functions
(ReadByte(), ReadChar(), etc) to read data from the message, just
as you would read data from a file. The Seek() and EndOfFile()
functions may also be used. Refer to the file I/O
functions in the Basic4GL Programmer's Guide
for more information.
Once you have finished with the message, you should discard it with CloseFile(), in order to free up resources.
[Example here]
There are two flags which indicate the current connection state of a connection:
When a client connection is created with NewConnection(), connected
and handshaking are both set.
If the connection succeeds, connected remains set, and handshaking
is cleared.
If the connection fails (either rejected by the server, or times
out), connected is cleared. (handshaking may
remain set though...)
Thus the code to establish a client connection might look something like this:
dim connection, address$, port ' Get connection details print "Address?:": address$ = input$ () print "Port?:": port = val (input$ ()) ' Attempt to connect to server printr "Connecting..." connection = NewConnection (address$, port) while ConnectionConnected (connection) and ConnectionHandshaking (connection): wend ' Check if succeeded if ConnectionConnected (connection) then printr "Connection succeeded" ' Do something with connection ' ... else printr "Connection failed" endif ' Close connection DeleteConnection (connection)
If you attempt to use a connection while in the handshaking stage the network engine will do it's best to accomodate this. Specifically:
When a server connection is created with AcceptConnection(), connected
is set and handshaking is cleared.
The connection is considered established and can be used
immediately.